﻿using System;
using System.Reflection;
using System.Web.UI;
using Axial.Reflection;
using System.Linq;
using System.Collections.Generic;
using System.Reflection.Emit;

namespace Axial
{
	public class WFScript : Control
	{
		public string MethodName { get; set; }

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			MethodInfo method = Page.GetMethod(MethodName);
			if (method == null)
				throw new Exception("Method " + MethodName + " not found in the page (method must be public)");

			List<MethodInfo> methodList = new List<MethodInfo> { method };
			for (int i = 0; i < methodList.Count; i++)
			{
				MethodInfo m = methodList[i];
				RegisterMethod(m);

				List<ILInstruction> instructions = new MethodBodyReader(m).Instructions;
				foreach (ILInstruction inst in instructions.Where(inst => new OpCode[] { OpCodes.Newobj, OpCodes.Initobj }.Contains(inst.Code)))
				{
					Type usedType;
					if (inst.Operand is ConstructorInfo)
						usedType = (inst.Operand as ConstructorInfo).DeclaringType;
					else
						usedType = (Type)inst.Operand;
					// web services need to be included, not output
					if (usedType.BaseType == typeof(System.Web.Services.Protocols.SoapHttpClientProtocol))
						RegisterWebService(usedType);
					if (usedType.Namespace == method.DeclaringType.Namespace)
						RegisterType(usedType);
				}

				OpCode[] methodCodes = new OpCode[] { OpCodes.Ldvirtftn, OpCodes.Ldftn, OpCodes.Call, OpCodes.Callvirt };
				foreach (ILInstruction inst in instructions.Where(inst => methodCodes.Contains(inst.Code)))
				{
					MethodInfo sub = (MethodInfo)inst.Operand;
					if (typeof(Page).IsAssignableFrom(sub.DeclaringType) && !methodList.Contains(sub))
						methodList.Add(sub);
				}
			}
		}

		private void RegisterWebService(Type type)
		{
			if (type.BaseType != typeof(System.Web.Services.Protocols.SoapHttpClientProtocol))
				return;
			if (Page.ClientScript.IsClientScriptBlockRegistered(typeof(WFClass), type.FullName))
				return;
			System.Web.Services.Protocols.SoapHttpClientProtocol proxy = (System.Web.Services.Protocols.SoapHttpClientProtocol)Activator.CreateInstance(type);

			// make sure AJAX javascript is referenced
			if (!Page.ClientScript.IsClientScriptBlockRegistered(typeof(WFServerScript), "PageMethod.js"))
				Page.ClientScript.RegisterClientScriptInclude(typeof(WFServerScript), "PageMethod.js",
					Page.ClientScript.GetWebResourceUrl(GetType(), "Axial.PageMethod.js"));
		}

		private void RegisterType(Type type)
		{
			if (Page.ClientScript.IsClientScriptBlockRegistered(typeof(WFClass), type.FullName))
				return;
			Page.ClientScript.RegisterClientScriptBlock(typeof(WFClass), type.FullName, JSWebTranslator.DissembleClass(type), true);
		}

		private void RegisterMethod(MethodInfo method)
		{
			if (Page.ClientScript.IsClientScriptBlockRegistered(typeof(WFScript), method.Name))
				return;

			// HACK?: assume any method name with odd characters is anonymous and doesn't need to be emitted
			if (JSWebTranslator.SanitizeIdentifier(method.Name) != method.Name)
				return;

			string[] args = Enumerable.Range(0, method.GetParameters().Length).Select(i => "arg" + i).ToArray();
            var parms = args.Cast<object>();
            if (!method.IsStatic)
                parms = new object[] { Page }.Concat(parms);
			JSWriter code = JSWebTranslator.DissembleMethod(method, parms.ToArray());
			JSWriter writer = new JSWriter();
			writer.AddPrivateFunction(method.Name, args.Length, code);
			Page.ClientScript.RegisterClientScriptBlock(typeof(WFScript), method.Name, writer.ToString(), true);
		}
	}
}
